/*
 * Copyright (C) 2017-2018 Samuel Wall
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package uk.co.samuelwall.materialtaptargetprompt.extras.backgrounds;

import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptBackground;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptOptions;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptText;
import uk.co.samuelwall.materialtaptargetprompt.extras.PromptUtils;
import uk.co.samuelwall.materialtaptargetprompt.utils.Constants;
import uk.co.samuelwall.materialtaptargetprompt.utils.DensityUtils;

import org.jetbrains.annotations.Range;

/**
 * {@link PromptBackground} implementation that renders the prompt background as a circle.
 *
 * @since 2021-06-17
 */
public class CirclePromptBackground extends PromptBackground {
    /**
     * The current circle centre position.
     */
    Point mPosition;

    /**
     * The current radius for the circle.
     */
    float mRadius;

    /**
     * The position for circle centre at 1.0 scale.
     */
    Point mBasePosition;

    /**
     * The radius for the circle at 1.0 scale.
     */
    float mBaseRadius;

    /**
     * The paint to use to render the circle.
     */
    Paint mPaint;

    /**
     * The alpha value to use at 1.0 scale.
     */
    @Range(from = 0, to = 255)
    int mBaseColourAlpha;

    /**
     * Constructor.
     */
    public CirclePromptBackground() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPosition = new Point();
        mBasePosition = new Point();
    }

    @Override
    public void setColour(int colour) {
        mPaint.setColor(new Color(colour));
        mBaseColourAlpha = Color.alpha(colour);
        mPaint.setAlpha(1f * mBaseColourAlpha / Constants.NUM_255);
    }

    @Override
    public void prepare(
            final PromptOptions options, final boolean isClipToBounds,
            final Rect clipBounds) {
        mPromptOptions = options;

        // Obtain values from the prompt options.
        final PromptText promptText = options.getPromptText();
        final RectFloat focalBounds = options.getPromptFocal().getBounds();
        final float focalCentreX = focalBounds.getCenter().getPointX();
        final float focalCentreY = focalBounds.getCenter().getPointY();
        final float focalPadding = options.getFocalPadding();
        final RectFloat textBounds = promptText.getBounds();
        final float textPadding = options.getTextPadding();
        final RectFloat clipBoundsInset88dp = new RectFloat(clipBounds);

        // Default material design offset prompt when more than 88dp inset
        final float inset88dp = 88f * DensityUtils.getDeviceAttr(options.getResourceFinder().getContext())
                .getAttributes().densityPixels;
        clipBoundsInset88dp.modify(clipBounds.left + inset88dp, clipBounds.top + inset88dp,
                clipBounds.right - inset88dp, clipBounds.bottom - inset88dp);

        // Is the focal centre more than 88dp from the clip bounds edge
        if ((focalCentreX - clipBoundsInset88dp.left > 0
                && focalCentreX - clipBoundsInset88dp.right < 0)
                || (focalCentreY - clipBoundsInset88dp.top > 0
                && focalCentreY - clipBoundsInset88dp.bottom < 0)) {
            // The circle position and radius is calculated based on three points placed around the
            // prompt: XY1, XY2 and XY3.
            // XY2: the text left side
            // XY3: the text right side
            // XY1: the furthest point on the focal target from the text centre x point
            // XY1
            float textWidth = textBounds.getWidth();

            // Calculate the X distance from the text centre x to focal centre x
            float distanceX = focalCentreX - textBounds.left + (textWidth / Constants.NUM_2);

            // Calculate how much percentage wise the focal centre x is from the text centre x to
            // the nearest text edge

            float percentageOffset = Constants.NUM_100 / textWidth * distanceX;

            // Angle is the above percentage of 90 degrees
            float angle = Constants.NUM_90 * (percentageOffset / Constants.NUM_100);

            // 0 degrees is right side middle
            // If text above target
            if (textBounds.top < focalBounds.top) {
                angle = Constants.NUM_180 - angle;
            } else {
                angle = Constants.NUM_180 + angle;
            }
            final Point furthestPoint = options.getPromptFocal().calculateAngleEdgePoint(angle, focalPadding);
            final float x1 = furthestPoint.getPointX();
            final float y1 = furthestPoint.getPointY();

            // XY2
            final float x2 = textBounds.left - textPadding;
            final float y2;

            // If text is above the target
            if (textBounds.top - focalBounds.top < 0) {
                y2 = textBounds.top;
            } else {
                y2 = textBounds.bottom;
            }

            // XY3
            // noinspection UnnecessaryLocalVariable
            final float y3 = y2;
            float x3 = textBounds.right + textPadding;

            // If the focal width is greater than the text width
            if (focalBounds.right - x3 > 0) {
                x3 = focalBounds.right + focalPadding;
            }

            // Calculate the position and radius
            final double offset = Math.pow(x2, Constants.NUM_2) + Math.pow(y2, Constants.NUM_2);
            final double bc = (Math.pow(x1, Constants.NUM_2)
                    + Math.pow(y1, Constants.NUM_2) - offset) / Constants.NUM_2F;
            final double cd = (offset - Math.pow(x3, Constants.NUM_2)
                    - Math.pow(y3, Constants.NUM_2)) / Constants.NUM_2F;
            final double det = (x1 - x2) * (y2 - y3) - (x2 - x3) * (y1 - y2);
            final double idet = 1 / det;
            mBasePosition.modify((float) ((bc * (y2 - y3) - cd * (y1 - y2)) * idet),
                    (float) ((cd * (x1 - x2) - bc * (x2 - x3)) * idet));
            mBaseRadius = (float) Math.sqrt(Math.pow(x2 - mBasePosition.getPointX(), Constants.NUM_2)
                    + Math.pow(y2 - mBasePosition.getPointY(), Constants.NUM_2));
        } else {
            mBasePosition.modify(focalCentreX, focalCentreY);

            // Calculate the furthest distance from the center based on the text size.
            final float length = Math.max(
                    Math.abs(textBounds.right - focalCentreX),
                    Math.abs(textBounds.left - focalCentreX)
                ) + textPadding;

            // Calculate the height based on the distance from the focal centre to the furthest text y position.

            float height = (focalBounds.getHeight() / Constants.NUM_2) + focalPadding + textBounds.getHeight();

            // Calculate the radius based on the calculated width and height
            mBaseRadius = (float) Math.sqrt(Math.pow(length, Constants.NUM_2) + Math.pow(height, Constants.NUM_2));
        }
        mPosition.modify(mBasePosition);
    }

    @Override
    public void update(float revealModifier, float alphaModifier) {
        final RectFloat focalBounds = mPromptOptions.getPromptFocal().getBounds();
        final float focalCentreX = focalBounds.getCenter().getPointX();
        final float focalCentreY = focalBounds.getCenter().getPointY();
        mRadius = mBaseRadius * revealModifier;
        mPaint.setAlpha(alphaModifier * mBaseColourAlpha / Constants.NUM_255);

        // Change the current centre position to be a position scaled from the focal to the base.
        mPosition.modify(focalCentreX + ((mBasePosition.getPointX() - focalCentreX) * revealModifier),
                focalCentreY + ((mBasePosition.getPointY() - focalCentreY) * revealModifier));
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawCircle(mPosition.getPointX(), mPosition.getPointY(), mRadius, mPaint);
    }

    @Override
    public boolean contains(float x, float y) {
        return PromptUtils.isPointInCircle(x, y, mPosition, mRadius);
    }
}
